/**
 *  Copyright 2012 Rafal Korytkowski
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.googlecode.rapidjunit;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Observable;
import java.util.Set;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.WorkbenchException;
import org.eclipse.ui.XMLMemento;

/**
 * Contains a model i.e. tested classes and test cases. It provides
 * save and load methods to persist the model.
 * 
 * @author Rafal Korytkowski
 */
public class RapidJUnitManager extends Observable {
	private final static String TAG_RAPIDJUNIT = "RapidJUnit";
	private final static String TAG_TESTED_CLASS = "TestedClass";
	private final static String TAG_TEST_CLASS = "TestClass";

	private final Map<IType, Set<IType>> tests;

	public RapidJUnitManager() {
		tests = new HashMap<IType, Set<IType>>();
	}

	/**
	 * @param path
	 * @return the test cases
	 */
	public Set<IType> getTestCases(String path) {
		IType tested = createIType(path);
		return getTestCases(tested);
	}

	/**
	 * @param tested
	 * @return the test cases
	 */
	public Set<IType> getTestCases(IType tested) {
		Set<IType> set = tests.get(tested);
		if (set != null) {
			return set;
		} else {
			return Collections.emptySet();
		}
	}

	/**
	 * @return the tested classes
	 */
	public Set<IType> getTestedClasses() {
		Set<IType> set = tests.keySet();
		if (set != null) {
			return set;
		} else {
			return Collections.emptySet();
		}
	}

	/**
	 * Adds the given tested class to the model.
	 * 
	 * @param testedClass
	 * @return true if added
	 */
	public boolean addTestedClass(IType testedClass) {
		if (!tests.containsKey(testedClass)) {
			tests.put(testedClass, new HashSet<IType>());
			setChanged();
			notifyObservers();
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Adds the test case to the tested class. If the tested class does not
	 * exist it will be created first.
	 * 
	 * @param testedClass
	 * @param testCase
	 * @return true if added
	 */
	public boolean addTestCase(IType testedClass, IType testCase) {
		Set<IType> set = tests.get(testedClass);
		if (set == null) {
			addTestedClass(testedClass);
		}
		set = tests.get(testedClass);
		if (set.add(testCase)) {
			setChanged();
			notifyObservers();
			return true;
		}
		return false;
	}

	/**
	 * Removes the tested class.
	 * 
	 * @param testedClass
	 * @return true if removed or false if does not exist
	 */
	public boolean removeTestedClass(IType testedClass) {
		if (tests.remove(testedClass) != null) {
			setChanged();
			notifyObservers();
			return true;
		}
		return false;
	}

	/**
	 * Removes the test case.
	 * 
	 * @param testedClass
	 * @param testCase
	 * @return true if removed or false if does not exist
	 */
	public boolean removeTestCase(IType testedClass, IType testCase) {
		Set<IType> set = tests.get(testedClass);
		if (set != null) {
			if (set.remove(testCase)) {
				setChanged();
				notifyObservers();
			}
			return false;
		}
		return false;
	}

	/**
	 * Saves the model to the file.
	 */
	public void save() {
		if (tests.isEmpty()) {
			return;
		}
		XMLMemento memento = XMLMemento.createWriteRoot(TAG_RAPIDJUNIT);
		for (IType testedClass : tests.keySet()) {
			IMemento tested = memento.createChild(TAG_TESTED_CLASS);
			tested.putTextData(testedClass.getResource().getFullPath()
					.toString());
			for (IType testCase : tests.get(testedClass)) {
				IMemento test = tested.createChild(TAG_TEST_CLASS);
				test.putTextData(testCase.getResource().getFullPath()
						.toString());
			}
		}
		FileWriter writer = null;
		try {
			writer = new FileWriter(getManagerFile());
			memento.save(writer);
		} catch (IOException e) {
			RapidJUnit.log(e, true);
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (IOException e) {
					RapidJUnit.log(e);
				}
			}
		}
	}

	/**
	 * Loads a model form the file.
	 */
	public void load() {
		FileReader reader = null;
		try {
			reader = new FileReader(getManagerFile());
			XMLMemento memento = XMLMemento.createReadRoot(reader);
			for (IMemento tested : memento.getChildren(TAG_TESTED_CLASS)) {
				IType testedClass = createIType(tested.getTextData());
				if (testedClass != null) {
					addTestedClass(testedClass);
					for (IMemento test : tested.getChildren(TAG_TEST_CLASS)) {
						IType testCase = createIType(test.getTextData());
						if (testCase != null) {
							addTestCase(testedClass, testCase);
						}
					}
				}
			}
		} catch (FileNotFoundException e) {
			// no file yet
		} catch (WorkbenchException e) {
			RapidJUnit.log(e, true);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {
					RapidJUnit.log(e);
				}
			}
		}
		setChanged();
		notifyObservers();
	}

	protected File getManagerFile() {
		return RapidJUnit.getLocation().append("rapidjunit.xml").toFile();
	}

	public IType createIType(String path) {
		IResource resource = ResourcesPlugin.getWorkspace().getRoot()
				.findMember(new Path(path));
		if (resource == null) {
			return null;
		} else {
			IJavaElement object = JavaCore.create(resource);
			if (!(object instanceof ICompilationUnit)) {
				return null;
			} else {
				return ((ICompilationUnit) object).findPrimaryType();
			}
		}
	}
}
